/*
 GDI Sample Code, By Billy Martin

 This sample program takes a text file and converts it into an image and displays that image in a window.
 It also allows the user to edit, print, and save the image as a text file.

 The image must be of a fixed size (50x50) with each line of the text file corresponding to a row
 of pixels in the image.  Four colors are supported: red, green, blue, and black.

 The purpose of this program is to demonstrate the basic features of the GDI as simply as possible.
*/

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

// to get documentation on ANY windows GDI function or structure,
// type the name of that function or structure into google followed by "site:msdn.microsoft.com"
#include <windows.h>

typedef int bool;
#define true 1
#define false 0

HBITMAP g_hbitmap;

// image is 50x50 pixels
#define BMP_WIDTH 50
#define BMP_HEIGHT 50

// image is 50x50 pixels.  stored in a text file, with each byte being one pixel.
// 'R' = red, 'G' = green, 'B' = blue, anything else = black
// each line in the text file ends with newline characters ( "\r\n" ) so we have to leave room for that
#define IMAGE_TEXT_BUFFER_SIZE   (BMP_HEIGHT * (BMP_WIDTH + 2))
char g_imagetext[IMAGE_TEXT_BUFFER_SIZE];

// here is a really big buffer for the image data
// image is 24 bits per pixel (3 bytes per pixel). 50x50 pixels.  plus some extra for padding at the end of lines
BYTE g_imagedata[BMP_HEIGHT * (BMP_WIDTH + 4) * 3];

void ConvertImageTextToImageData()
{
	int sourcebyte = 0;
	int destbyte = 0;
	int linebyte = 0;

	while(sourcebyte < IMAGE_TEXT_BUFFER_SIZE) {
		BYTE red = 0;
		BYTE green = 0;
		BYTE blue = 0;

		if(g_imagetext[sourcebyte] == 'R') red = 255;
		if(g_imagetext[sourcebyte] == 'G') green = 255;
		if(g_imagetext[sourcebyte] == 'B') blue = 255;

		g_imagedata[destbyte+0] = blue;
		g_imagedata[destbyte+1] = green;
		g_imagedata[destbyte+2] = red;
		destbyte += 3;
		sourcebyte++;
		linebyte++;

		if(linebyte == BMP_WIDTH) {
			// we have reached the end of the current line, so we must start a new line
			// this requires padding the data with zeroes to ensure the line ends on a 4-byte multiple
			while(destbyte % 4 != 0) {
				g_imagedata[destbyte] = 0;
				destbyte++;
			}
			// the source should have a carriage feed and newline ("\r\n") here, so we will skip past that
			sourcebyte++;
			sourcebyte++;

			linebyte = 0;
		}
	}
}

void CopyImageDataToBitmap(HDC hdc, HBITMAP hbitmap)
{
	BITMAPINFO bmi;

	ZeroMemory(&bmi, sizeof(bmi));

	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_1rw2.asp
	bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
	bmi.bmiHeader.biWidth = BMP_WIDTH;
	bmi.bmiHeader.biHeight = -BMP_HEIGHT;
	bmi.bmiHeader.biPlanes = 1;
	bmi.bmiHeader.biBitCount = 24;
	bmi.bmiHeader.biCompression = BI_RGB;
	bmi.bmiHeader.biSizeImage = 0;
	bmi.bmiHeader.biXPelsPerMeter = 0;
	bmi.bmiHeader.biYPelsPerMeter = 0;
	bmi.bmiHeader.biClrUsed = 0;
	bmi.bmiHeader.biClrImportant = 0;

	// see: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_0qk3.asp
	SetDIBits(hdc, hbitmap, 0, BMP_HEIGHT, &g_imagedata, &bmi, 0);
}

void RefreshWindow(HWND hwnd)
{
	// invalidates the client area of the window, forcing a re-draw via WM_PAINT
	InvalidateRect(hwnd, NULL, FALSE);
}

/////////////// FILE I/O ////////////////////////////////////////////

void ShowLastErrorMsgBox(const char* caption)
{
	LPVOID pmsg = NULL;
	int err = GetLastError();
	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR) &pmsg, 0, NULL);
	MessageBox(NULL, (const char*)pmsg, caption, MB_ICONERROR);
	LocalFree(pmsg);
}

void ReadImageFile(const char* filename)
{
	DWORD bytesRead;
	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfile.asp
	HANDLE h = CreateFile(
		filename,
		GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
		NULL);

	if(h == INVALID_HANDLE_VALUE) {
		ShowLastErrorMsgBox(filename);
		return;
	}

	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/readfile.asp
	bytesRead;
	ReadFile(h, &g_imagetext, IMAGE_TEXT_BUFFER_SIZE, &bytesRead, NULL);

	if(bytesRead < sizeof(g_imagetext)) {
		MessageBox(NULL, "didn't read all of the bytes", "Warning", MB_ICONERROR);
	}

	CloseHandle(h);
}

void WriteImageFile(const char* filename)
{
	DWORD bytesWrote;
	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/createfile.asp
	HANDLE h = CreateFile(
		filename,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
		NULL);

	if(h == INVALID_HANDLE_VALUE) {
		ShowLastErrorMsgBox(filename);
		return;
	}

	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/writefile.asp
	bytesWrote;
	WriteFile(h, &g_imagetext, IMAGE_TEXT_BUFFER_SIZE, &bytesWrote, NULL);

	if(bytesWrote < sizeof(g_imagetext)) {
		MessageBox(NULL, "didn't write all of the bytes", "Warning", MB_ICONERROR);
	}

	CloseHandle(h);
}

/*

Open/Save File Dialog Functions

creates the nice dialog box UI so a user can choose a file to save/load

arguments:
fnamebuf    :  where the name of the selected file is stored (you can put in a default value here)
bufsize     :  the size of fnamebuf
initialDir  :  the path to start at.  Sorta works.  If left blank or NULL, windows will try to figure it out intelligently
filter      :  looks like this:  "text files (*.txt)\0*.txt\0\0\0"    i.e.  "<desc1>\0<filter1.1>;<filter1.2>; ... \0<desc2>\0<filter2.1>;<filter2.2>; ... \0 ... \0\0"
extension   :  looks like "txt"  Just some sort of default extension.  Can be at most 3 characters.
hwnd        :  The parent window.  These are modal dialog boxes.

see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/commondialogboxlibrary/commondialogboxreference/commondialogboxstructures/openfilename.asp

*/

bool OpenFileDialog(char* fnamebuf, int bufsize, const char* intialDir, const char* filter, const char* extension, HWND hwnd)
{
	static OPENFILENAME fileinfo;

	fileinfo.lStructSize = sizeof(OPENFILENAME);
	fileinfo.hwndOwner = hwnd;
	fileinfo.lpstrFile = fnamebuf;
	fileinfo.nMaxFile = bufsize;
	fileinfo.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	fileinfo.lpstrInitialDir = intialDir;
	fileinfo.lpstrFilter = filter;
	fileinfo.lpstrDefExt = extension;

	return (bool)GetOpenFileName(&fileinfo);
}

bool SaveFileDialog(char* fnamebuf, int bufsize, const char* intialDir, const char* filter, const char* extension, HWND hwnd)
{
	static OPENFILENAME fileinfo;

	fileinfo.lStructSize = sizeof(OPENFILENAME);
	fileinfo.hwndOwner = hwnd;
	fileinfo.lpstrFile = fnamebuf;
	fileinfo.nMaxFile = bufsize;
	fileinfo.Flags = OFN_HIDEREADONLY;
	fileinfo.lpstrInitialDir = intialDir;
	fileinfo.lpstrFilter = filter;
	fileinfo.lpstrDefExt = extension;

	return (bool)GetSaveFileName(&fileinfo);
}

void HelpBox(HWND hwnd)
{
	const char* helptext =
		"Click the mouse on a pixel to change its color.\r\n"
		"Keyboard Commands:\r\n"
		"S:  save image\r\n"
		"O:  open image\r\n"
		"P:  print image\r\n"
		"Q:  quit\r\n"
		"X:  quit\r\n"
		"F1: help\r\n"
		"Alt+Enter: switch to fullscreen mode\r\n"
		;
	MessageBox(hwnd, helptext, "Help", 0);
}

/////////////// MOUSE STUFF ////////////////////////////////////////////

int g_windowwidth = 0;
int g_windowheight = 0;

bool g_editchange = false; // tracks if the image is edited, so we can let user know if he closes the window

bool g_fullscreen = false; // are we in fullscreen mode?

bool g_trackmouse = false; // true if the mouse is in your window
// mouse position within window
int g_mx = -1;
int g_my = -1;
// mouse position within image (highlighted pixel)
int g_ix = -1;
int g_iy = -1;

int WindowToImageX(int x)
{
	if(x < 0) return -1;
	return (x * BMP_WIDTH) / g_windowwidth;
}

int WindowToImageY(int y)
{
	if(y < 0) return -1;
	return (y * BMP_HEIGHT) / g_windowheight;
}


void MouseMove(HWND hwnd, int x, int y)
{
	HDC hdc;
	HDC bmphdc;
	int ix;
	int iy;
	// convert window coordinates to image coordinates
	ix = WindowToImageX(x);
	iy = WindowToImageY(y);

	if(ix != g_ix || iy != g_iy) {
		g_ix = ix;
		g_iy = iy;

		// this function updates the window directly without doing a Refresh/RePaint

		hdc = GetDC(hwnd);
		bmphdc = CreateCompatibleDC(hdc);

		CopyImageDataToBitmap(hdc, g_hbitmap);
		SelectObject(bmphdc, g_hbitmap);
		// set the pixel the mouse is over (if any) to grey.  We don't have to check bounds, SetPixel does this for us.
		SetPixel(bmphdc, g_ix, g_iy, RGB(128,128,128));

		SetStretchBltMode(hdc, COLORONCOLOR);
		StretchBlt(hdc, 0, 0, g_windowwidth, g_windowheight, bmphdc, 0, 0, BMP_WIDTH, BMP_HEIGHT, SRCCOPY);

		DeleteDC(bmphdc);
		ReleaseDC(hwnd, hdc);
	}
}


void MouseLeftClick(HWND hwnd, int x, int y)
{
	// convert window coordinates to image coordinates
	int ix = WindowToImageX(x);
	int iy = WindowToImageY(y);

	// get the current color of the pixel, and change it to
	if(ix >= 0 && ix < BMP_WIDTH && iy > 0 && iy < BMP_HEIGHT) {
		char c = g_imagetext[iy * (BMP_WIDTH + 2) + ix];
		switch(c) {
			case 'R':	c = 'G';	break;
			case 'G':	c = 'B';	break;
			case 'B':	c = ' ';	break;
			default:	c = 'R';	break;
		}
		g_imagetext[iy * (BMP_WIDTH + 2) + ix] = c;
		g_editchange = true;
		RefreshWindow(hwnd);
	}
}

void MouseRightClick(HWND hwnd, int x, int y)
{
	// convert window coordinates to image coordinates
	int ix = WindowToImageX(x);
	int iy = WindowToImageY(y);

	// get the current color of the pixel, and change it to
	if(ix >= 0 && ix < BMP_WIDTH && iy > 0 && iy < BMP_HEIGHT) {
		char c = g_imagetext[iy * (BMP_WIDTH + 2) + ix];
		switch(c) {
			case 'R':	c = ' ';	break;
			case 'G':	c = 'R';	break;
			case 'B':	c = 'G';	break;
			default:	c = 'B';	break;
		}
		g_imagetext[iy * (BMP_WIDTH + 2) + ix] = c;
		g_editchange = true;
		RefreshWindow(hwnd);
	}

}

bool SaveUI(HWND hwnd)
{
	char filename[300] = "default.txt";
	if(SaveFileDialog(filename, 300, NULL, "text files (*.txt)\0*.txt\0\0\0", "txt", NULL)) {
		WriteImageFile(filename);
		g_editchange = false;
		return true;
	}
	return false;
}

bool LoadUI(HWND hwnd)
{
	char filename[300] = "default.txt";
	if(OpenFileDialog(filename, 300, NULL, "text files (*.txt)\0*.txt\0\0\0", "txt", NULL)) {
		ReadImageFile(filename);
		g_editchange = false;
		RefreshWindow(hwnd);
		return true;
	}
	return false;
}

bool PrintUI(HWND hwnd)
{
	// print the image to the printer

	bool retval = false;
	PRINTDLG pd;
	HDC winhdc;
	HDC bmphdc;
	int rval = 0;
	int pagewidth;
	int pageheight;
	int xoffset;
	int yoffset;

	ZeroMemory(&pd, sizeof(pd));
	pd.lStructSize	= sizeof(pd);
	pd.hwndOwner	= NULL;
	pd.hDevMode		= NULL;
	pd.hDevNames	= NULL;
	pd.Flags		= PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC | PD_NOSELECTION | PD_NOPAGENUMS;
	pd.nCopies		= 1;
	pd.nFromPage	= 1;
	pd.nToPage		= 1;
	pd.nMinPage		= 1;
	pd.nMaxPage		= 1;

	// see:  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/commondialogboxlibrary/commondialogboxreference/commondialogboxfunctions/printdlg.asp
	if(PrintDlg(&pd) != 0) {

		DOCINFO di;
		ZeroMemory(&di, sizeof(di));
		di.cbSize = sizeof(di);
		di.lpszDocName = "dxvgdi";
		di.lpszOutput = NULL;
		di.lpszDatatype = NULL;
		di.fwType = 0;

		rval = 0;

		rval = StartDoc(pd.hDC, &di);

		if(rval == SP_ERROR) {
			ShowLastErrorMsgBox("StartDoc error");
			DeleteDC(pd.hDC);
			if(pd.hDevMode != NULL) GlobalFree(pd.hDevMode);
			if(pd.hDevNames != NULL) GlobalFree(pd.hDevNames);
			return false;
		}

		rval = StartPage(pd.hDC);

		if(rval <= 0) {
			ShowLastErrorMsgBox("StartPage error");
			EndDoc(pd.hDC);
			DeleteDC(pd.hDC);
			if(pd.hDevMode != NULL) GlobalFree(pd.hDevMode);
			if(pd.hDevNames != NULL) GlobalFree(pd.hDevNames);
			return false;
		}

		pagewidth = GetDeviceCaps(pd.hDC, PHYSICALWIDTH);
		pageheight = GetDeviceCaps(pd.hDC, PHYSICALHEIGHT);
		xoffset = GetDeviceCaps(pd.hDC, PHYSICALOFFSETX);
		yoffset = GetDeviceCaps(pd.hDC, PHYSICALOFFSETY);

		winhdc = GetDC(hwnd);
		bmphdc = CreateCompatibleDC(pd.hDC);
		ConvertImageTextToImageData();
		CopyImageDataToBitmap(bmphdc, g_hbitmap);
		SelectObject(bmphdc, g_hbitmap);

		// we invert the colors when printing, so that black shows up as white, etc.
		// so to do this we just use NOTSRCCOPY instead of SRCCOPY and GDI does the work for us
		// This is done because while light on dark is optimal for screen display,
		// dark on light is optimal for printed output.
		SetStretchBltMode(pd.hDC, COLORONCOLOR);
		rval = StretchBlt(pd.hDC, xoffset, yoffset, pagewidth - xoffset * 2, pageheight - yoffset * 2, bmphdc, 0, 0, BMP_WIDTH, BMP_HEIGHT, NOTSRCCOPY);

		DeleteDC(bmphdc);
		ReleaseDC(hwnd, winhdc);

		EndPage(pd.hDC);
		EndDoc(pd.hDC);

		DeleteDC(pd.hDC);
		retval = true;
	}

	if(pd.hDevMode != NULL) GlobalFree(pd.hDevMode);
	if(pd.hDevNames != NULL) GlobalFree(pd.hDevNames);

	return retval;
}

void CharacterPress(HWND hwnd, int key)
{
	// a character was typed
	if(key == 'S' || key == 's') {
		SaveUI(hwnd);
	}
	if(key == 'O' || key == 'o') {
		LoadUI(hwnd);
	}
	if(key == 'P' || key == 'p') {
		PrintUI(hwnd);
	}
	if(key == 'X' || key == 'x' || key == 'Q' || key == 'q') {
		SendMessage(hwnd, WM_CLOSE, 0, 0);
	}
}

void AltCharacterPress(HWND hwnd, int key)
{
	// a character was typed while pushing ALT
}

void KeyboardPress(HWND hwnd, int key)
{
	// a special key was pressed
	if(key == VK_F1) {
		HelpBox(hwnd);
	}
}

WINDOWPLACEMENT g_windowplacement; // here we store the position of the window when we go to fullscreen mode, so we can restore it when we return to windowed mode.

void SwitchToFullscreen(HWND hwnd)
{
	// switch to fullscreen mode
	int x = 0;
	int y = 0;
	int sx = GetSystemMetrics(SM_CXSCREEN);
	int sy = GetSystemMetrics(SM_CYSCREEN);
	HMONITOR hmon;

	MONITORINFO minfo;
	ZeroMemory(&minfo, sizeof(minfo));
	minfo.cbSize = sizeof(minfo);
	// find out which monitor the window is in, and fullscreen to that monitor
	hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
	if(GetMonitorInfo(hmon, &minfo) != 0) {
		x = minfo.rcMonitor.left;
		y = minfo.rcMonitor.top;
		sx = minfo.rcMonitor.right - x;
		sy = minfo.rcMonitor.bottom - y;
	}

	// save the current window position
	ZeroMemory(&g_windowplacement, sizeof(g_windowplacement));
	g_windowplacement.length = sizeof(g_windowplacement);
	GetWindowPlacement(hwnd, &g_windowplacement);

	ShowWindow(hwnd, SW_HIDE);
	SetWindowLong(hwnd, GWL_STYLE, WS_POPUP);
	SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TOPMOST);
	SetWindowPos(hwnd, HWND_TOP, x, y, sx, sy, 0);
	ShowWindow(hwnd, SW_SHOW);
	g_fullscreen = true;
}

void SwitchToWindowed(HWND hwnd)
{
	// switch to windowed mode
	ShowWindow(hwnd, SW_HIDE);
	SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
	SetWindowLong(hwnd, GWL_EXSTYLE, 0);
	SetWindowPlacement(hwnd, &g_windowplacement);
	ShowWindow(hwnd, SW_SHOW);

	g_fullscreen = false;
}

void AltKeyboardPress(HWND hwnd, int key)
{
	// a special key was pressed while holding down ALT
	if(key == VK_RETURN) {
		if(!g_fullscreen) {
			SwitchToFullscreen(hwnd);
		} else {
			SwitchToWindowed(hwnd);
		}
	}
}
/////////////// TIMER STUFF ////////////////////////////////////////////

void DoEverySecond(HWND hwnd)
{
	// this function gets called once every second by the windows timer.
	// you can set a timer to make your game do things without user input (like saving a backup of the current game, doing maintenance, or whatever)
	// timers can be used for events up to precision of around 50 milliseconds (i.e. once every 50 milliseconds, give or take 50 milliseconds)
	// anything more precise than that requires the use of multimedia timers wich are more complicated.

	// and, of course, the timer function won't necessarily be called at the right time if the system is busy.
	// the timer is APPROXIMATE, make no assumtions about the actual frequency that this function gets called.

	// if you want a function to simply be called as often as possible (i.e. for animation), you can put it into your message loop in WinMain
	// there is a special technique involved here (you have to use PeekMessage instead of GetMessage) but it is not complicated

}

/////////////// WINDOW PROCEDURE ////////////////////////////////////////////

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	HDC bmphdc;
	PAINTSTRUCT ps;
	RECT rc;

	switch(msg) {

		case WM_CREATE:
			{
				// create the bitmap when the window is created
				hdc = GetDC(hwnd);
				g_hbitmap = CreateCompatibleBitmap(hdc, BMP_WIDTH, BMP_HEIGHT);
				SetTimer(hwnd, 1, 1000, NULL); // sets a timer, which will send the WM_TIMER message to this window every 1000 milliseconds
			}
			return 0;

		case WM_PAINT:
			{
				hdc = BeginPaint(hwnd, &ps);
				bmphdc = CreateCompatibleDC(hdc);
				ConvertImageTextToImageData();
				CopyImageDataToBitmap(bmphdc, g_hbitmap);
				SelectObject(bmphdc, g_hbitmap);
				SetStretchBltMode(hdc, COLORONCOLOR);
				StretchBlt(hdc, 0, 0, g_windowwidth, g_windowheight, bmphdc, 0, 0, BMP_WIDTH, BMP_HEIGHT, SRCCOPY);
				DeleteDC(bmphdc);
				EndPaint(hwnd, &ps);
			}
			return 0;

		case WM_SIZE:
			{
				// the window has been re-sized
				GetClientRect(hwnd, &rc);
				g_windowwidth = rc.right - rc.left;
				g_windowheight = rc.bottom - rc.top;
			}
			return 0;

		case WM_TIMER:
			{
				// this happens once every second
				DoEverySecond(hwnd);
			}
			return 0;

		case WM_MOUSEMOVE:
			{
				if(!g_trackmouse) {
					TRACKMOUSEEVENT tme;
					tme.cbSize = sizeof(tme);
					tme.dwFlags = TME_LEAVE;
					tme.hwndTrack = hwnd;
					tme.dwHoverTime = HOVER_DEFAULT;
					TrackMouseEvent(&tme);
					g_trackmouse = true;
				}
				g_mx = LOWORD(lParam);
				g_my = HIWORD(lParam);

				MouseMove(hwnd, g_mx, g_my);
			}
			return 0;

		case WM_MOUSELEAVE:
			{
				g_trackmouse = false;
				g_mx = -1;
				g_my = -1;

				MouseMove(hwnd, g_mx, g_my);
			}
			return 0;

		case WM_LBUTTONDOWN:
			{
				g_mx = LOWORD(lParam);
				g_my = HIWORD(lParam);
				MouseLeftClick(hwnd, g_mx, g_my);
			}
			return 0;

		case WM_RBUTTONDOWN:
			{
				g_mx = LOWORD(lParam);
				g_my = HIWORD(lParam);
				MouseRightClick(hwnd, g_mx, g_my);
			}
			return 0;

		case WM_CHAR:
			{
				CharacterPress(hwnd, wParam);
			}
			return 0;

		case WM_SYSCHAR:
			{
				AltCharacterPress(hwnd, wParam);
			}
			return 0;

		case WM_KEYDOWN:
			// see: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceui40/html/cerefWM_KEYDOWN.asp
			{
				KeyboardPress(hwnd, wParam);
			}
			return 0;

		case WM_SYSKEYDOWN:
			{
				AltKeyboardPress(hwnd, wParam);
			}
			return 0;

		case WM_CLOSE:
			{
				bool allowclose = false;
				//user has pushed the close button, so we should check if the image is saved
				if(g_editchange) {
					switch(MessageBox(hwnd, "Image has changed.  Save changes?", "Exiting...", MB_YESNOCANCEL)) {
						case IDYES:
							if(SaveUI(hwnd)) allowclose = true;
							break;
						case IDNO:
							allowclose = true;
							break;
						case IDCANCEL:
							allowclose = false;
							break;
					}
					if(allowclose) {
						// allow the close to occur.  The message will be passed along to
						// DefWindowProc which will handle the destroying of the window.
						break;
					} else {
						// prevent the close from happening.  DefWindowProc will not be called.
						return 0;
					}
				}
			}
			break;


		case WM_DESTROY:
			{
				DeleteObject(g_hbitmap);
				// this will post the windows message that gets you out of the message loop in WinMain
				PostQuitMessage(0);
			}
			return 0;
	}

	// this function must be called to handle all the window messages that we don't handle.
	return DefWindowProc(hwnd, msg, wParam, lParam);
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	WNDCLASS wndclass;
	HWND hwnd;
	MSG msg;

	wndclass.style			= CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc	= WndProc;
	wndclass.cbClsExtra    = 0;
	wndclass.cbWndExtra    = 0;
	wndclass.hInstance     = hInstance;
	wndclass.hIcon         = NULL;
	wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = NULL;
	wndclass.lpszMenuName  = NULL;
	wndclass.lpszClassName = "dxvgdi";

	RegisterClass(&wndclass);

	hwnd = CreateWindowEx(0, "dxvgdi", "Sample GDI Program (Push F1 for help)", WS_OVERLAPPEDWINDOW, 10, 10, 500, 500, NULL, NULL, hInstance, NULL);

	ShowWindow(hwnd, TRUE);
	UpdateWindow(hwnd);

	// note that I create the window FIRST and then load the data from the file.
	// for this simple program it doesn't matter, but, if you have a more complex program that has to load a lot
	// of files and do a bunch of processing at the beginning, it is very important to create and show your window
	// before doing all that work.  Otherwise, your user will double click to launch your application, and nothing will
	// happen for a few seconds, and they'll get annoyed and think windows is being stupid, and they'll double click
	// and launch your application again.
	// It is very important to give your user the feeling of responsiveness.  Even showing an empty blank window
	// (as we are doing here) is better than showing no window at all.  Of course, ideally your window could say
	// "Loading..." or something if your program will take a really long time to load, but that's beyond the scope
	// of this demo.

	ReadImageFile("default.txt");
	RefreshWindow(hwnd);

	// Window message loop
	while(GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;

}
